package com.parking.order.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.parking.common.core.model.PageInfo;
import com.parking.common.core.model.RedisKey;
import com.parking.common.core.model.StateData;
import com.parking.common.core.util.DateUtil;
import com.parking.common.web.util.RedisUtil;
import com.parking.order.client.dto.*;
import com.parking.order.config.RabbitMQConfig;
import com.parking.order.dao.OrderInfoDao;
import com.parking.order.model.EntranceInfo;
import com.parking.order.dao.EntranceInfoDao;
import com.parking.order.model.OrderInfo;
import com.parking.order.model.dto.AddEnterInfoDto;
import com.parking.order.model.dto.AddOutInfoDto;
import com.parking.order.model.dto.EntranceDto;
import com.parking.order.model.exception.OrderException;
import com.parking.order.model.exception.OrderExceptionCode;
import com.parking.order.model.param.AddEnterInfoParam;
import com.parking.order.model.param.AddOutInfoParam;
import com.parking.order.model.param.FindEntranceByOrderNumParam;
import com.parking.order.model.param.FindEntranceParam;
import com.parking.order.service.IEntranceInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.parking.order.state.CommonOrderStateChange;
import com.parking.order.util.BasicInfoUtil;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 吴彦祖
 * @since 2023年11月28日
 */
@Service
public class EntranceInfoServiceImpl extends ServiceImpl<EntranceInfoDao, EntranceInfo> implements IEntranceInfoService {
    /**
     * 订单信息数据层
     * */
    @Autowired
    private OrderInfoDao orderInfoDao;
    /**
     * 进出场信息数据层
     * */
    @Autowired
    private EntranceInfoDao entranceInfoDao;
    /**
     * Redis工具类
     * */
    @Autowired
    private RedisUtil redisUtil;
    /**
     * 订单状态改变方法
     * */
    @Autowired
    private CommonOrderStateChange commonOrderStateChange;
    /**
     * 其他模块调用工具类
     * */
    @Autowired
    private BasicInfoUtil basicInfoUtil;

    /**
     * 消息队列工具类
     * */
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 添加入场信息
     * 参数 AddEnterInfoForm
     *      stallInfoId 车位信息Id
     *      carPlate 车牌号
     *      entranceImg 入场图片
     *      entranceEnterTime 入场时间
     *      certifiedId 物业Id
     *
     * 返回 AddEnterInfoDto
     *      entranceInfoId 出入场信息Id
     *      userInfoId     用户信息Id
     *      userInfoName   用户名称
     *      certifiedInfoId 物业信息Id
     *      certifiedInfoName 物业名称
     *      certifiedInfoTel 物业电话
     *      certifiedInfoAddress 物业地址
     *      stallInfoId     车位信息Id
     *      stallInfo         车位号
     *      entranceInfoPlate 入场车牌号
     *      stallType        普通车位 0普通车位 1充电车位
     *      entranceEnterTime 入场时间
     *      entranceImg       入场图片
     *      orderInfoId       订单Id
     *      orderInfoNum      订单编号
     *      extraInfo         额外信息 提醒是否是该订单的第一次进入
     * */
    @Transactional
    @Override
    public AddEnterInfoDto addEnterInfo(AddEnterInfoParam param) throws Exception {
        // certifiedId & stallInfoId -> certifiedInfoId 相同
        Integer certifiedInfoId = basicInfoUtil.getCertifiedAccount(param.getCertifiedId()).getCertifiedInfoId();

        // 可以进场 = 该车位存在唯一的可开始或已开始订单
        OrderInfo order = orderInfoDao.selectOne(new QueryWrapper<OrderInfo>().eq("stall_info",param.getStallInfo()).eq("certified_info_id",certifiedInfoId).in("order_info_state", Arrays.asList(StateData.ORDER_STATE_BEGIN,StateData.ORDER_STATE_CAN_BEGIN)));
        if(order==null) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ENTRANCE_INFO_NO_ORDER_INFO);
        if(param.getEntranceEnterTime().isBefore(order.getStartTime())) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ENTRANCE_INFO_NOT_COME_BEFORE_START);
        if(!param.getEntranceEnterTime().isBefore(order.getEndTime())) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ENTRANCE_INFO_WRONG_ORDER_INFO);
        if(!order.getUserCarPlate().equals(param.getCarPlate())) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ENTRANCE_INFO_WRONG_CAR_PLATE);

        // 入场信息不能倒置
        long num = entranceInfoDao.selectCount(new QueryWrapper<EntranceInfo>().eq("entrance_info_plate", param.getCarPlate()).isNull("entrance_out_time"));
        if(num==1) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ENTRANCE_INFO_TWO_IN_NO_OUT);
        EntranceInfo entrancePoss = entranceInfoDao.selectOne(new QueryWrapper<EntranceInfo>().eq("entrance_info_plate", param.getCarPlate()).orderByDesc("entrance_out_time").last("LIMIT 1"));
        if(entrancePoss!=null){
            if(!(entrancePoss.getEntranceOutTime().isBefore(param.getEntranceEnterTime()))) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ENTRANCE_INFO_PAST_IN);
        }

        // 添加入场信息
        // userInfoId entranceInfoPlate entranceEnterTime certifiedInfoId certifiedInfoName entranceImg stallInfoId
        EntranceInfo entrance = new EntranceInfo(order.getUserInfoId(),order.getUserCarPlate(),param.getEntranceEnterTime(),order.getCertifiedInfoId(),order.getCertifiedInfoName(),param.getEntranceImg(),order.getStallInfoId(),order.getOrderInfoNum());
        entrance.setUpdateTime(entrance.getEntranceEnterTime());
        entrance.setStallInfo(order.getStallInfo());
        entranceInfoDao.insert(entrance);

        // 若第一次进入 修改订单状态
        boolean isFirst = Objects.equals(order.getOrderInfoState(), StateData.ORDER_STATE_CAN_BEGIN);
        if(isFirst){
            order.setOrderInfoState(StateData.ORDER_STATE_BEGIN);
            order.setOrderUpdateTime(param.getEntranceEnterTime());
            orderInfoDao.updateById(order);

            String orderLogsDescribe = "车辆到达车位 开始使用服务";
            Integer logType = StateData.ORDER_LOG_PERSON_TYPE_CERTIFIED;
            String logger = order.getCertifiedInfoName();
            rabbitTemplate.convertAndSend("orderLog",order.getOrderInfoNum()+","+orderLogsDescribe+","+logType+","+logger);
        }

        // 返回Dto
        UserInfoDto user = basicInfoUtil.getUserInfo(order.getUserInfoId());
        AddEnterInfoDto dto = new AddEnterInfoDto(entrance.getEntranceInfoId(),order.getUserInfoId(),user.getUserInfoName(),order.getCertifiedInfoId(),order.getCertifiedInfoName(),order.getCertifiedInfoTel(),order.getCertifiedInfoAddress(), entrance.getStallInfoId(),entrance.getEntranceInfoPlate(),order.getStallInfo(),order.getStallType(),param.getEntranceEnterTime(),param.getEntranceImg(),order.getOrderInfoId(),order.getOrderInfoNum());
        if(isFirst) dto.setExtraInfo("车辆第一次进入车位 订单"+dto.getOrderInfoNum()+"进入已开始状态");
        else dto.setExtraInfo("");
        return dto;
    }
    /**
     * 添加出场信息
     * 参数 AddOutInfoForm
     *      stallInfoId 车位信息Id
     *      carPlate 车牌号
     *      entranceOutTime 出场时间
     *      certifiedId 物业Id
     *
     * 返回 AddOutInfoDto
     *      entranceInfoId 出入场信息Id
     *      userInfoId     用户信息Id
     *      userInfoName   用户名称
     *      certifiedInfoId 物业信息Id
     *      certifiedInfoName 物业名称
     *      certifiedInfoTel 物业电话
     *      certifiedInfoAddress 物业地址
     *      stallInfoId     车位信息Id
     *      entranceInfoPlate 入场车牌号
     *      stallInfo         车位号
     *      stallType        普通车位 0普通车位 1充电车位
     *      entranceEnterTime 入场时间
     *      entranceImg       入场图片
     *      entranceOutTime   出场时间
     *      orderInfoId       订单Id
     *      orderInfoNum      订单编号
     *      extraInfo         额外信息 提醒是否生成超时订单
     * */
    @Transactional
    @Override
    public AddOutInfoDto addOutInfo(AddOutInfoParam param) throws Exception {
        // certifiedId & stallInfoId -> certifiedInfoId 相同
        Integer certifiedInfoId = basicInfoUtil.getCertifiedAccount(param.getCertifiedId()).getCertifiedInfoId();

        // 存在唯一该车位的有入场信息但没有出场信息的出入场纪录
        EntranceInfo entrance = entranceInfoDao.selectOne(new QueryWrapper<EntranceInfo>().eq("stall_info",param.getStallInfo()).eq("certified_info_id",certifiedInfoId).isNull("entrance_out_time"));
        if(entrance==null) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ENTRANCE_INFO_NO_MATCH);
        if(!entrance.getEntranceEnterTime().isBefore(param.getEntranceOutTime())) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ENTRANCE_INFO_ENTER_OUT_TIME_CONFLICT);
        if(!entrance.getEntranceInfoPlate().equals(param.getCarPlate())) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ENTRANCE_INFO_WRONG_CAR_PLATE);

        // 添加出场信息
        entrance.setEntranceOutTime(param.getEntranceOutTime());
        entrance.setUpdateTime(param.getEntranceOutTime());
        entranceInfoDao.updateById(entrance);

        // 若超时 修改订单状态
        OrderInfo order = orderInfoDao.selectOne(new QueryWrapper<OrderInfo>().eq("stall_info",param.getStallInfo()).eq("certified_info_id",certifiedInfoId).in("order_info_state", Arrays.asList(StateData.ORDER_STATE_BEGIN,StateData.ORDER_STATE_SHOULD_END)));
        boolean isOvertime = Objects.equals(order.getOrderInfoState(), StateData.ORDER_STATE_SHOULD_END);
        if(isOvertime){
            order.setOrderInfoState(StateData.ORDER_STATE_END);
            order.setOrderUpdateTime(param.getEntranceOutTime());
            orderInfoDao.updateById(order);

            String orderLogsDescribe = "超时车辆已离开车位 自动生成超时订单";
            Integer logType = StateData.ORDER_LOG_PERSON_TYPE_CERTIFIED;
            String logger = order.getCertifiedInfoName();
            rabbitTemplate.convertAndSend("orderLog",order.getOrderInfoNum()+","+orderLogsDescribe+","+logType+","+logger);

            rabbitTemplate.convertAndSend("orderOvertime",entrance.getEntranceInfoId());

            int orderOvertimeNum = Integer.parseInt(redisUtil.get(RedisKey.certifiedOrderOvertime(order.getCertifiedInfoId())));
            redisUtil.set(RedisKey.certifiedOrderOvertime(order.getCertifiedInfoId()),String.valueOf(orderOvertimeNum-1));
        }

        // 返回Dto
        UserInfoDto user = basicInfoUtil.getUserInfo(order.getUserInfoId());
        AddOutInfoDto dto = new AddOutInfoDto(entrance.getEntranceInfoId(),order.getUserInfoId(),user.getUserInfoName(),order.getCertifiedInfoId(),order.getCertifiedInfoName(),order.getCertifiedInfoTel(),order.getCertifiedInfoAddress(), entrance.getStallInfoId(),entrance.getEntranceInfoPlate(),order.getStallInfo(),order.getStallType(),entrance.getEntranceEnterTime(),entrance.getEntranceImg(),entrance.getEntranceOutTime(),order.getOrderInfoId(),order.getOrderInfoNum());
        if(isOvertime) dto.setExtraInfo("订单超时 已生成超时订单 在支付超时订单前无法下单");
        return dto;
    }

    /**
     * 根据订单编号查询进出场信息
     * 参数 出租客Id renterId
     *     用户Id  userId
     *      平台Id platformId
     *     订单编号 orderInfoNum
     * 返回 List<String> 入场时间 - 出场时间
     * */
    @Override
    public List<String> findEntranceByOrderNum(FindEntranceByOrderNumParam param) throws Exception {
        checkUserPlatformRentersForOrderInfoNum(param.getUserId(),param.getPlatformId(),param.getRentersId(),param.getOrderInfoNum());

        // 查询List<String> 若isEmpty则抛异常
        List<EntranceInfo> entrances = entranceInfoDao.selectList(new QueryWrapper<EntranceInfo>().eq("order_info_num", param.getOrderInfoNum()).orderByAsc("entrance_enter_time"));
        if(entrances.isEmpty()) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ORDER_NO_ENTRANCE_INFO);
        List<String> infos = new ArrayList<>();
        for (EntranceInfo entrance : entrances) {
            String time = "";
            String enterTime = DateUtil.localDateTimeToString(entrance.getEntranceEnterTime());
            if(entrance.getEntranceOutTime()!=null){
                String outTime = DateUtil.localDateTimeToString(entrance.getEntranceOutTime());
                time = enterTime + " - " + outTime;
            }else time = enterTime + " -";
            infos.add(time);
        }
        return infos;
    }

    /**
     * 判断平台Id 用户Id 出租客Id 是否合法
     *     以及与订单编号orderInfoNum是否契合
     * 参数 出租客Id renterId
     *     用户Id  userId
     *      平台Id platformId
     *     订单编号 orderInfoNum
     * */
    public void checkUserPlatformRentersForOrderInfoNum(Integer userId,Integer platformId,Integer rentersId,String orderInfoNum) throws Exception {
        // 平台Id校验 用户Id校验 出租客Id校验
        if(platformId!=null){
            basicInfoUtil.getPlatform(platformId);
        }
        Integer userInfoId = null;
        if(userId!=null){
            UserAccountDto user = basicInfoUtil.getUser(userId);
            userInfoId = user.getUserInfoId();
        }
        Integer rentersInfoId = null;
        if(rentersId!=null){
            RentersDto renters = basicInfoUtil.getRenters(rentersId);
            rentersInfoId = renters.getRentersInfoId();
        }

        OrderInfo order = orderInfoDao.selectOne(new QueryWrapper<OrderInfo>().eq("order_info_num", orderInfoNum));
        if(order==null) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ORDER_NOT_EXISTS);
        // userInfoId与orderInfo匹配
        if(userInfoId!=null) {
            if(!Objects.equals(order.getUserInfoId(), userInfoId)) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ORDER_NOT_BELONG_TO_THIS_USER);
        }
        // renterInfoId与orderInfo的stallInfoId匹配
        if(rentersInfoId!=null){
            StallDto stall = basicInfoUtil.getStallInfo2(order.getStallInfoId());
            if(!Objects.equals(stall.getRentersInfoId(), rentersInfoId)) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ORDER_NOT_BELONG_TO_THIS_RENTERS);
        }
    }

    /**
     * 物业查询进出场信息日志
     * 参数：certifiedId 物业账号Id
     *      pageNum 页数
     *      pageSize 每页大小
     *      startTime 开始时间
     *      endTime   结束时间
     *      stallInfo 车位号
     *      orderInfoNum 订单编号
     *      entranceInfoPlate 车牌号
     *      isOut 是否出场 0已出场 1未出场
     *
     * 返回 entranceInfoId 出入场信息Id
     *      orderInfoNum 订单编号
     *      userInfoId 用户信息Id
     *      userInfoName 用户名称
     *      userInfoTel 用户电话
     *      entranceInfoPlate 车牌号
     *       stallInfoId 车位信息Id
     *       stallInfo 车位号
     *       stallType 车位类型 0普通车位 1充电车位
     *       entranceEnterTime 入场图片
     *       entranceOutTime 出场图片
     *       entranceImg 入场图片
     * */
    @Override
    public PageInfo findEntrance(FindEntranceParam param) throws Exception {
        boolean isStartTime = param.getStartTime()!=null;
        boolean isEndTime = param.getEndTime()!=null;
        boolean isOrderInfoNum = param.getOrderInfoNum()!=null;
        boolean isStallInfo = param.getStallInfo()!=null;
        boolean isEntranceInfoPlate = param.getEntranceInfoPlate()!=null;
        boolean isIsOut = param.getIsOut()!=null;

        // 判断 开始时间<结束时间
        if(isStartTime&&isEndTime) throw new OrderException(OrderExceptionCode.ORDER_EXCEPTION_CODE_ENTRANCE_INFO_ENTER_OUT_TIME_CONFLICT);

        // 查询
        Page<EntranceInfo> page = new Page<>(param.getPageNum(),param.getPageSize());
        QueryWrapper<EntranceInfo> wrapper = new QueryWrapper<>();
        CertifiedAccountDto certified = basicInfoUtil.getCertifiedAccount(param.getCertifiedId());
        wrapper.eq("certified_info_id",certified.getCertifiedInfoId());
        if(isOrderInfoNum) wrapper.eq("order_info_num",param.getOrderInfoNum());
        if(isEntranceInfoPlate) wrapper.eq("entrance_info_plate",param.getEntranceInfoPlate());
        if(isStallInfo) wrapper.eq("stall_info",param.getStallInfo());
        if(isIsOut){
            if(Objects.equals(param.getIsOut(), StateData.ENTRANCE_INFO_IS_OUT)){
                wrapper.isNotNull("entrance_out_time");
            }
            if(Objects.equals(param.getIsOut(), StateData.ENTRANCE_INFO_IS_NOT_OUT)){
                wrapper.isNull("entrance_out_time");
            }
        }
        // startTime endTime
        if(isStartTime){
            wrapper.and(rapper->rapper.isNull("entrance_out_time").or().ge("entrance_out_time",param.getStartTime()));
        }
        if(isEndTime){
            wrapper.le("entrance_enter_time",param.getEndTime());
        }
        wrapper.orderByDesc("update_time");
        entranceInfoDao.selectPage(page, wrapper);

        // 返回Dto
        List<EntranceDto> dtos = new ArrayList<>();
        for (EntranceInfo entrance : page.getRecords()) {
            EntranceDto dto = BeanUtil.toBean(entrance,EntranceDto.class);
            // userInfoName userInfoTel
            UserInfoDto user = basicInfoUtil.getUserInfo(dto.getUserInfoId());
            dto.setUserInfoName(user.getUserInfoName());
            dto.setUserInfoTel(user.getUserInfoTel());
            // stallType
            OrderInfo order = orderInfoDao.selectOne(new QueryWrapper<OrderInfo>().eq("order_info_num", entrance.getOrderInfoNum()));
            dto.setStallType(order.getStallType());
            dtos.add(dto);
        }
        PageInfo pageInfo = BeanUtil.toBean(page, PageInfo.class);
        pageInfo.setPages(page.getPages());
        pageInfo.setTotal(page.getTotal());
        pageInfo.setRecords(dtos);
        return pageInfo;
    }
}
